// GTA RWTEX file loader example by The_GTA.
// Contains useful utilities if you want to load RenderWare textures natively.
// Uses DK22Pac's plugin sdk.

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>

#include <plugin.h>
#include <game_sa/CSprite2d.h>
#include <game_sa/RenderWare.h>
#include <game_sa/CFileMgr.h>

using namespace plugin;

template <typename retType, typename... Args>
struct sa_func_def
{
    typedef retType (__cdecl*function_t)( Args... );

    inline sa_func_def( DWORD ptr )
    {
        this->func_ptr = (void*)ptr;
    }

    inline retType operator () ( Args... args ) const
    {
        return ( (function_t)this->func_ptr )( args... );
    }

private:
    void *func_ptr;
};

static const sa_func_def <RwTexture*, RwStream*> RwTextureStreamReadRockstar( 0x00730E60 );

static RwTexture* loadRWTEXFromDisc( const char *path )
{
    RwTexture *texHandle = NULL;

    RwStream *imgStream = RwStreamOpen( rwSTREAMFILENAME, rwSTREAMREAD, (void*)path );

    if ( imgStream )
    {
        texHandle = RwTextureStreamReadRockstar( imgStream );

        RwStreamClose( imgStream, NULL );
    }

    return texHandle;
}

/*=========================================================
    RwFindTexture

    Arguments:
        name - primary name of the texture you want to find
        maskName - name of the alpha mask (unused shit)
    Purpose:
        Scans the global and the current TXD containers for a
        matching texture. If the TXD is found in the current TXD
        (m_findInstanceRef), it is referenced and returned to the
        caller. If not, the global texture container is researched.
        Textures in the global environment are not referenced upon
        return but are put into the current texture container if it
        is set. The global texture environment is either like a
        temporary storage for textures or a routine to dynamically
        create them and give to models.
    Note:
        GTA:SA does not use the global texture routine.
    Binary offsets:
        (1.0 US): 0x007F3AC0
        (1.0 EU): 0x007F3B00
=========================================================*/
typedef RwTexture* (__cdecl *RwReadGlobalTexture_t) ( const char *name, const char *secName );
typedef RwTexture* (__cdecl *RwReadLocalTexture_t) ( const char *name );

struct rwTextureManager
{
    RwLinkList                      globalTxd;                              // 0
    void*                           pad;                                    // 8
    RwFreeList*                     txdStruct;                              // 12
    RwTexDictionary*                current;                                // 16
    RwReadGlobalTexture_t           readGlobal;                             // 20
    RwReadLocalTexture_t            readLocal;                              // 24
};

inline int GetRWTextureManagerOffset( void )
{
    return *(int*)0x00C97B4C;
}

// Macro to get a plugin struct from a RenderWare object.
template <typename structType, typename rwobjType>
structType* RW_PLUGINSTRUCT( rwobjType *obj, size_t pluginOffset )
{
    return (structType*)( (char*)obj + pluginOffset );
}

RwTexture* RwFindTexture( const char *name, const char *maskName )
{
    void *rwInterface = RwEngineInstance;

    // First try to find thing in our textures :)
    {
        std::string texPath = CFileMgr::ms_rootDirName;
        texPath += "textures/";
        texPath += name;
        texPath += ".rwtex";

        if ( RwTexture *texHandle = loadRWTEXFromDisc( texPath.c_str() ) )
        {
            // We can just return it.
            // We do not care about caching it.
            return texHandle;
        }
    }

    rwTextureManager *rwTexMan = RW_PLUGINSTRUCT <rwTextureManager> ( rwInterface, GetRWTextureManagerOffset() );

    // The local store will reference textures
    if ( RwTexture *tex = rwTexMan->readLocal( name ) )
    {
        tex->refCount++;
        return tex;
    }
    
    // We could also get a texture from the global store.
    if ( RwTexture *tex = rwTexMan->readGlobal( name, maskName ) )
    {
        if ( RwTexDictionary *txd = rwTexMan->current )
        {
            // This is a caching feature.
            RwTexDictionaryRemoveTexture( tex );
            RwTexDictionaryAddTexture( txd, tex );
        }

        return tex;
    }

    // If we have not found anything, we tell the system about an error
    RwError err;
    err.pluginID = 0x01;
    err.errorCode = 0x16;

    // Actually, there is a missing texture handler; it is void though
    RwErrorSet( &err );
    return NULL;
}

class RWTEXGlobalReplacePlugin
{
    injector::scoped_jmp findTexture_hook;

public:
    inline RWTEXGlobalReplacePlugin()
    {
        findTexture_hook = injector::scoped_jmp( 0x007F3AC0, RwFindTexture, true );

        Events::initRwEvent += [] {
            return;
        };

        Events::shutdownRwEvent += [] {
            return;
        };
    }
} globtex_repl_plg;